import logging
import arrow
import xlwt

from datetime import datetime, timedelta
from io import BytesIO

import requests
from dateutil.relativedelta import relativedelta
from django.http import FileResponse
from requests import ReadTimeout
from requests.auth import HTTPBasicAuth
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_mongoengine import generics, serializers

from utilities.meta_view import Success
from apps.global_config.serializer import get_global_config
from apps.log_center.filters import LogFilter
from apps.log_center.serializer import OperationLogSerializer
from apps.log_center.display import LOG_TYPE_DISPLAY_NAME, LOG_HEADER_DISPLAY_NAME
from apps.log_center.utils import get_operator
from models.audit_log import AuditLog
from wass.settings import LOG_DOWNLOAD_API
from utilities.custom_exception import ApiTimeOut, ApiServerError
from wass.display import (
    MODULE_NAME_DICT,
    SUBMODULE_NAME_DICT,
    ACTION_NAME_DICT,
    MODULE_SUBMODULE_CONTAINS,
    LOGIN_MODULE,
    LANGUAGE_CODE
)
from utilities.log import audit_log
from utilities.enums import LogModuleName, AuditLogAction, LogTypeName, DATETIME_FORMAT
from utilities.utils import iso_format_to_local

logger = logging.getLogger(f"custom.{__name__}")


class LogOperationConfigView(APIView):
    """返回日志相关的筛选映射"""

    allowed_methods = ("GET",)

    def get(self, request, *args, **kwargs):
        module = request.query_params.get("module", "")
        if not module:
            config = {
                "module": self.reverse_dict(MODULE_NAME_DICT),
                "submodule": self.reverse_dict(SUBMODULE_NAME_DICT),
                "action": self.reverse_dict(ACTION_NAME_DICT),
            }
        else:
            submodules = MODULE_SUBMODULE_CONTAINS.get(module, [])
            config = {
                "module": self.reverse_dict(MODULE_NAME_DICT),
                "submodule": {
                    value: key
                    for key, value in SUBMODULE_NAME_DICT.items()
                    if key in submodules
                },
                "action": self.reverse_dict(ACTION_NAME_DICT),
            }
        return Response(data=config, status=200)

    @staticmethod
    def reverse_dict(d: dict):
        return {v: k for k, v in d.items()}


class LogOperationView(generics.ListAPIView):
    """操作日志列表"""

    serializer_class = OperationLogSerializer

    def filter_queryset(self, queryset):
        query_filter = LogFilter(self.request.query_params, queryset=queryset)
        return query_filter.qs

    def get_queryset(self):
        now = datetime.now()
        today = datetime(now.year, now.month, now.day)
        default_from_date = today - relativedelta(months=6) + timedelta(days=1)
        audit_modules = list(MODULE_NAME_DICT.keys())
        return AuditLog.objects.filter(
            module__in=audit_modules,
            time__gte=int(default_from_date.timestamp()),
            **get_operator(self.request)
        ).order_by("-time")


class LogLoginView(generics.ListAPIView):
    """登录日志列表"""

    serializer_class = OperationLogSerializer

    def filter_queryset(self, queryset):
        query_filter = LogFilter(self.request.query_params, queryset=queryset)
        return query_filter.qs

    def get_queryset(self):
        now = datetime.now()
        today = datetime(now.year, now.month, now.day)
        default_from_date = today - relativedelta(months=6) + timedelta(days=1)
        return AuditLog.objects.filter(
            module=LOGIN_MODULE,
            time__gte=int(default_from_date.timestamp()),
            **get_operator(self.request)
        ).order_by("-time")


class LogDownloadView(APIView):
    allowed_methods = ("POST",)
    type_mapping = {
        "login": "登陆",
        "operation": "操作",
    }

    def post(self, request, *args, **kwargs):
        if request.method not in self.allowed_methods:
            return Response(status=405)

        log_type_list = self.validate_log_type(request.data.get("log_types"))
        start_date = request.data.get("start_date", "")
        end_date = request.data.get("end_date", "")
        if not all([start_date, end_date]):
            raise serializers.serializers.ValidationError(f"未选择日志记录开始日期或者结束结束")

        date_time_low = arrow.get(start_date).int_timestamp
        date_time_high = arrow.get(end_date).int_timestamp

        audit_logs = AuditLog.objects.filter(
            time__gte=date_time_low,
            time__lt=date_time_high,
            **self.get_module_query(log_type_list),
            **get_operator(self.request)
        )

        return self.action(log_type_list, start_date, end_date, audit_logs)

    def action(self, log_type_list, start_date, end_date, audit_logs):
        wb = xlwt.Workbook("UTF-8")
        op_st = wb.add_sheet(LOG_TYPE_DISPLAY_NAME.get(LANGUAGE_CODE, {}).get(0))
        lg_st = wb.add_sheet(LOG_TYPE_DISPLAY_NAME.get(LANGUAGE_CODE, {}).get(1))

        op_header = LOG_HEADER_DISPLAY_NAME.get(LANGUAGE_CODE, {}).get(0, {})
        lg_header = LOG_HEADER_DISPLAY_NAME.get(LANGUAGE_CODE, {}).get(1, {})

        for i, item in enumerate(op_header.values()):
            op_st.write(0, i, item)
        for i, item in enumerate(lg_header.values()):
            lg_st.write(0, i, item)

        i = 0
        j = 0
        for item in audit_logs:
            y = 0
            if item.type == LogTypeName.login.value or item.type == LogTypeName.logout.value:
                st = lg_st
                k_map = lg_header
                i += 1
                x = i
            else:
                st = op_st
                k_map = op_header
                j += 1
                x = j

            for k in k_map:
                v = getattr(item, k, None)
                if k == 'time':
                    v = iso_format_to_local(v)
                elif k == 'desc':
                    v = v.get('detail', "")
                elif k == 'module':
                    v = MODULE_NAME_DICT.get(v, 'unknown')
                elif k == 'type':
                    v = SUBMODULE_NAME_DICT.get(v, 'unknown')
                st.write(x, y, v)
                y += 1

        bio = BytesIO()

        wb.save(bio)
        bio.seek(0)
        response = FileResponse(
            bio, as_attachment=True, content_type="application/octet-stream"
        )
        response[
            'Content-Disposition'
        ] = f"attachment; filename={iso_format_to_local(start_date)}_{iso_format_to_local(end_date)}.xls"

        data = {
            "module": LogModuleName.log_center.value,
            "action": AuditLogAction.add.value,
            "type": LogTypeName.log_management.value,
            "desc": {
                "detail": f"下载日志，"
                f"日志类型：{','.join([self.type_mapping.get(log_type) for log_type in log_type_list])}，"
                f"时间区间：{iso_format_to_local(start_date)}-{iso_format_to_local(end_date)}"
            },
        }
        audit_log(request=self.request, **data)
        return response

    def validate_log_type(self, log_type_list):
        log_type_allowed = tuple(self.type_mapping.keys())
        for k in log_type_list:
            if k not in log_type_allowed:
                raise serializers.serializers.ValidationError(
                    f"日志类型仅支持{self.type_mapping.values()}"
                )
        return log_type_list

    @staticmethod
    def request_log_download(log_type, date, timeout=20):
        url = LOG_DOWNLOAD_API["url"]
        username = LOG_DOWNLOAD_API["username"]
        password = LOG_DOWNLOAD_API["password"]
        params = {"type": log_type, "date": date}
        try:
            response = requests.get(
                url=url,
                params=params,
                auth=HTTPBasicAuth(username, password),
                timeout=timeout,
            )
        except ReadTimeout:
            logger.warning(f"API:{url} TIMEOUT:{timeout} Params:{params}")
            raise ApiTimeOut
        except Exception as e:
            logger.warning(f"API:{url} requestError:{str(e)} Params:{params}")
            raise ApiServerError
        else:
            return response

    @staticmethod
    def get_module_query(log_type_list):
        module_list = []
        for log_type in log_type_list:
            if log_type == 'operation':
                module_list.extend(
                    list(
                        {name.value for name in LogModuleName}
                        - {LogModuleName.login.value}
                    )
                )
            else:
                module_list.append(log_type)
        if not module_list:
            raise serializers.serializers.ValidationError(f"未选择日志类型")

        return {'module__in': module_list}


class LogDeleteView(LogDownloadView):
    allowed_methods = ("POST",)

    def action(self, log_type_list, start_date, end_date, audit_logs):
        audit_logs.delete()

        data = {
            "module": LogModuleName.log_center.value,
            "action": AuditLogAction.del_.value,
            "type": LogTypeName.log_management.value,
            "desc": {
                "detail": f"删除日志，"
                f"日志类型：{','.join([self.type_mapping.get(log_type) for log_type in log_type_list])}"
                f"时间区间：{iso_format_to_local(start_date)}-{iso_format_to_local(end_date)}"
            },
        }
        audit_log(request=self.request, **data)

        return Success()


class LogSwitchView(APIView):
    """
    get:
        查询日志记录功能开关状态
    post:
        开启、关闭日志记录功能
    """

    def get(self, request):
        obj = get_global_config()
        login_log = getattr(obj, "login_log", True)
        operate_log = getattr(obj, "operate_log", True)
        return Response(
            data={"status": "success", "errcode": 0, "data": {"login_log": login_log, "operate_log": operate_log}},
            status=200
        )

    def post(self, request):
        login_log = self.request.data.get("login_log", -1)
        operate_log = self.request.data.get("operate_log", -1)
        obj = get_global_config()
        obj.login_log = login_log
        obj.operate_log = operate_log
        obj.save()
        if operate_log:
            data = {
                "module": LogModuleName.log_center.value,
                "action": AuditLogAction.add.value,
                "type": LogTypeName.log_management.value,
                "desc": {
                    "detail": f"开启记录操作日志, {'开启' if login_log else '关闭'}记录登录日志"
                },
            }
            audit_log(request=self.request, **data)
        return Response(
            data={"status": "success", "errcode": 0,
                  "data": {"login_log": obj.login_log, "operate_log": obj.operate_log}},
            status=200
        )
